package uuid

import (
	"errors"
	"net"
	"strings"
	"sync/atomic"
	"time"

	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/system"
)

const (
	defaultStartTimeStr          = "2019-01-01T00:00:00.000Z"
	defaultStartTimeNano   int64 = 1546300800000000000
	defaultStartTimeSecond int64 = 1546300800
)
const (
	Second64 = 0 // 以秒作为时间划分，使用年限最长，每秒频次中等
	Mill64   = 1 // 以纳秒作为时间划分，使用频次最高，配合seqBit
	Second32 = 2 // 以秒作为时间划分，长度32位，每秒频次最低
)

var (
	EmptyInnerIPV4Err = errors.New("inner IPv4 empty")
)

// https://github.com/beinan/fastid/blob/master/fastid.go
// https://github.com/bwmarrin/snowflake
type SnowFlake struct {
	target      byte
	timeBits    uint
	seqBits     uint
	machineBits uint
	timeMask    int64
	seqMask     int64
	machineID   int64
	lastID      int64
	startEpoch  int64
}

// CommonConfig is the recommended setting for most applications
//  40 bits timestamp
//  7  bits seq
//  16 bits machine id
func NewSnowFlakeSecond64(seqBits uint, machineBits uint, startTime string) *SnowFlake {
	if 63-machineBits-seqBits != 32 {
		panic("Snowflake Second64 Seq + machine must = 31")
	}
	return NewSnowFlakeWithMachine(32, seqBits, machineBits, Second64, GetNo(), startTime)
}
func NewSnowFlakeMill64(seqBits, machineBits uint, startTime string) *SnowFlake {
	if 63-machineBits-seqBits != 40 {
		panic("Snowflake Nano 64 Seq + machine must = 23")
	}
	return NewSnowFlakeWithMachine(40, seqBits, machineBits, Mill64, GetNo(), startTime)
}
func NewSnowFlakeSecond32(seqBits, machineBits uint, startTime string) *SnowFlake {
	if 31-machineBits-seqBits != 27 {
		panic("Snowflake Second32 Seq + machine must = 4")
	}
	return NewSnowFlakeWithMachine(27, seqBits, machineBits, Second32, GetNo(), startTime)
}

func NewSnowFlakeWithMachine(timeBits, seqBits, machineBits uint, target byte, machineID int16, startTime string) *SnowFlake {
	if target == Second32 {
		if timeBits+seqBits+machineBits != 31 {
			panic("Snowflake Bit length must = 31")
		}
	} else {
		if timeBits+seqBits+machineBits != 63 {
			panic("Snowflake Bit length must = 63")
		}
	}
	machineIDMask := ^(int64(-1) << machineBits)
	var startEpoch int64
	switch target {
	case Second32:
		startEpoch = getStartEpochSec(startTime)
	case Second64:
		startEpoch = getStartEpochSec(startTime)
	case Mill64:
		startEpoch = getStartEpochMilli(startTime)
	}
	return &SnowFlake{
		target:      target,
		timeBits:    timeBits,
		seqBits:     seqBits,
		machineBits: machineBits,
		timeMask:    ^(int64(-1) << timeBits),
		seqMask:     ^(int64(-1) << seqBits),
		machineID:   int64(machineID) & machineIDMask,
		lastID:      0,
		startEpoch:  startEpoch,
	}
}

func (c *SnowFlake) getCurrentTimestamp() int64 {
	switch c.target {
	case Second32:
		fallthrough
	case Second64:
		// devided by 2^30 (~10^9, nano to second)
		return (time.Now().Unix() - c.startEpoch) & c.timeMask
	case Mill64:
		// devided by 2^20 (~10^6, nano to milliseconds)
		return (time.Now().UnixNano()>>20 - c.startEpoch) & c.timeMask
	}
	return 0
}

//GenInt64ID generates unique int64 IDs with the setting in the methond owner
func (c *SnowFlake) GenerateId() int64 {
	for {
		localLastID := atomic.LoadInt64(&c.lastID)
		seq := c.GetSeqFromID(localLastID)
		lastIDTime := c.GetTimeFromID(localLastID)
		now := c.getCurrentTimestamp()
		if now > lastIDTime {
			seq = 0
		} else if seq >= c.seqMask {
			time.Sleep(time.Duration(0xFFFFF - (time.Now().UnixNano() & 0xFFFFF)))
			continue
		} else {
			seq++
		}

		newID := now<<(c.machineBits+c.seqBits) + seq<<c.machineBits + c.machineID
		if atomic.CompareAndSwapInt64(&c.lastID, localLastID, newID) {
			return newID
		}
		time.Sleep(time.Duration(20))
	}
}

//GetSeqFromID extracts seq number from an existing ID
func (c *SnowFlake) GetSeqFromID(id int64) int64 {
	return (id >> c.machineBits) & c.seqMask
}

//GetTimeFromID extracts timestamp from an existing ID
func (c *SnowFlake) GetTimeFromID(id int64) int64 {
	return id >> (c.machineBits + c.seqBits)
}

func GetNo() int16 {
	//take the lower 16bits of IP address as Machine ID
	if ip, err := getIP(); err == nil {
		return (int16(ip[2]) << 8) + int16(ip[3])
	}
	return 0
}

func getStartEpochMilli(startTime string) int64 {
	if startTime == "" {
		startTime = defaultStartTimeStr
	}
	var startEpochTime, err = time.Parse(time.RFC3339, startTime)
	if err != nil {
		return defaultStartTimeNano >> 20
	} else {
		return startEpochTime.UnixNano() >> 20
	}
}

func getStartEpochSec(startTime string) int64 {
	if startTime == "" {
		startTime = defaultStartTimeStr
	}
	var startEpochTime, err = time.Parse(time.RFC3339, startTime)
	if err != nil {
		return defaultStartTimeSecond
	} else {
		return startEpochTime.Unix()
	}
}

func getIP() (net.IP, error) {
	ip, err := system.LocalIPV4()
	if err != nil {
		return nil, err
	}
	ipS := strings.Split(ip, ".")
	ipB := net.IP{utils.ToByte(ipS[0]), utils.ToByte(ipS[1]), utils.ToByte(ipS[2]), utils.ToByte(ipS[3])}
	// 内网ip
	if ipB[0] == 10 || ipB[0] == 172 && (ipB[1] >= 16 && ipB[1] < 32) || ipB[0] == 192 && ipB[1] == 168 {
		return ipB, nil
	} else {
		return nil, EmptyInnerIPV4Err
	}
}
